/**
 * @author nttdocomo
 */
define(function(require) {
	require("../util/observable");
	require("../util/bindable");
	require("../util/mixedCollection");
	aries.klass("aries.selection.Model", aries.Class.extend({

		/**
		 * A fast reset of the selections without firing events, updating the ui, etc.
		 * For private usage only.
		 * @private
		 */
		clearSelections : function() {
			// reset the entire selection to nothing
			this.selected.clear();
			this.lastSelected = null;
			this.setLastFocused(null);
		},

		/**
		 * Deselects all records in the view.
		 * @param {Boolean} suppressEvent True to suppress any deselect events
		 */
		deselectAll : function(suppressEvent) {
			var me = this, selections = me.getSelection(), i = 0, len = selections.length, start = me.getSelection().length;

			me.bulkChange = true;
			for (; i < len; i++) {
				me.doDeselect(selections[i], suppressEvent);
			}
			delete me.bulkChange;
			// fire selection change only if the number of selections differs
			me.maybeFireSelectionChange(me.getSelection().length !== start);
		},

		// records can be an index, a record or an array of records
		doDeselect : function(records, suppressEvent) {
			var me = this, selected = me.selected, i = 0, len, record, attempted = 0, accepted = 0;

			if (me.locked || !me.store) {
				return false;
			}

			if ( typeof records === "number") {
				records = [me.store.getAt(records)];
			} else if (!_.isArray(records)) {
				records = [records];
			}

			function commit() {++accepted;
				selected.remove(record);
			}

			len = records.length;

			for (; i < len; i++) {
				record = records[i];
				if (me.isSelected(record)) {
					if (me.lastSelected == record) {
						me.lastSelected = selected.last();
					}++attempted;
					me.onSelectChange(record, false, suppressEvent, commit);
				}
			}

			// fire selchange if there was a change and there is no suppressEvent flag
			me.maybeFireSelectionChange(accepted > 0 && !suppressEvent);
			return accepted === attempted;
		},

		doSelect : function(records, keepExisting, suppressEvent) {
			var me = this, record;

			if (me.locked || !me.store) {
				return;
			}
			if ( typeof records === "number") {
				records = [me.store.getAt(records)];
			}
			if (me.selectionMode == "SINGLE" && records) {
				record = records.length ? records[0] : records;
				me.doSingleSelect(record, suppressEvent);
			} else {
				me.doMultiSelect(records, keepExisting, suppressEvent);
			}
		},

		doSingleSelect : function(record, suppressEvent) {
			var me = this, changed = false, selected = me.selected;

			if (me.locked) {
				return;
			}
			// already selected.
			// should we also check beforeselect?
			if (me.isSelected(record)) {
				return;
			}

			function commit() {
				me.bulkChange = true;
				if (selected.getCount() > 0 && me.doDeselect(me.lastSelected, suppressEvent) === false) {
					delete me.bulkChange;
					return false;
				}
				delete me.bulkChange;

				selected.add(record);
				me.lastSelected = record;
				changed = true;
			}


			me.onSelectChange(record, true, suppressEvent, commit);

			if (changed) {
				if (!suppressEvent) {
					me.setLastFocused(record);
				}
				me.maybeFireSelectionChange(!suppressEvent);
			}
		},

		getLastFocused : function() {
			return this.lastFocused;
		},

		/**
		 * Returns an array of the currently selected records.
		 * @return {Ext.data.Model[]} The selected records
		 */
		getSelection : function() {
			return this.selected.getRange();
		},

		initialize : function(cfg) {
			var me = this;

			cfg = cfg || {};
			$.extend(me, cfg);

			//me.addEvents(
			/**
			 * @event
			 * Fired after a selection change has occurred
			 * @param {Ext.selection.Model} this
			 * @param {Ext.data.Model[]} selected The selected records
			 *///'selectionchange',
			/**
			 * @event
			 * Fired when a row is focused
			 * @param {Ext.selection.Model} this
			 * @param {Ext.data.Model} oldFocused The previously focused record
			 * @param {Ext.data.Model} newFocused The newly focused record
			 */
			//'focuschange');

			me.modes = {
				SINGLE : true,
				SIMPLE : true,
				MULTI : true
			};

			// sets this.selectionMode
			me.setSelectionMode(cfg.mode || me.mode);

			// maintains the currently selected records.
			me.selected = new aries.util.MixedCollection();

			//aries.util.Observable.prototype.initialize.apply(this,arguments);
		},

		/**
		 * Returns true if the specified row is selected.
		 * @param {Ext.data.Model/Number} record The record or index of the record to check
		 * @return {Boolean}
		 */
		isSelected : function(record) {
			record = _.isNumber(record) ? this.store.getAt(record) : record;
			return this.selected.indexOf(record) !== -1;
		},

		// fire selection change as long as true is not passed
		// into maybeFireSelectionChange
		maybeFireSelectionChange : function(fireEvent) {
			var me = this;
			if (fireEvent && !me.bulkChange) {
				me.trigger('selectionchange', me, me.getSelection());
			}
		},

		// @abstract
		onLastFocusChanged : function(oldFocused, newFocused) {
			this.trigger('focuschange', this, oldFocused, newFocused);
		},

		refresh : function() {
			var me = this, toBeSelected = [], oldSelections = me.getSelection(), len = oldSelections.length, selection, change, i = 0, lastFocused = this.getLastFocused();

			// check to make sure that there are no records
			// missing after the refresh was triggered, prune
			// them from what is to be selected if so
			for (; i < len; i++) {
				selection = oldSelections[i];
				if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
					toBeSelected.push(selection);
				}
			}

			// there was a change from the old selected and
			// the new selection
			if (me.selected.getCount() != toBeSelected.length) {
				change = true;
			}

			me.clearSelections();

			if (me.store.indexOf(lastFocused) !== -1) {
				// restore the last focus but supress restoring focus
				this.setLastFocused(lastFocused, true);
			}

			if (toBeSelected.length) {
				// perform the selection again
				me.doSelect(toBeSelected, false, true);
			}

			me.maybeFireSelectionChange(change);
		},

		/**
		 * Sets a record as the last focused record. This does NOT mean
		 * that the record has been selected.
		 * @param {Ext.data.Model} record
		 */
		setLastFocused : function(record, supressFocus) {
			var me = this, recordBeforeLast = me.lastFocused;

			me.lastFocused = record;

			// Only call the changed method if in fact the selected record *has* changed.
			if (record !== recordBeforeLast) {
				me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
			}
		},

		/**
		 * Sets the current selectionMode.
		 * @param {String} selModel 'SINGLE', 'MULTI' or 'SIMPLE'.
		 */
		setSelectionMode : function(selMode) {
			selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
			// set to mode specified unless it doesnt exist, in that case
			// use single.
			this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
		},

		// Provides differentiation of logic between MULTI, SIMPLE and SINGLE
		// selection modes. Requires that an event be passed so that we can know
		// if user held ctrl or shift.
		selectWithEvent : function(record, e, keepExisting) {
			var me = this;

			switch (me.selectionMode) {
				case 'MULTI':
					if (e.ctrlKey && me.isSelected(record)) {
						me.doDeselect(record, false);
					} else if (e.shiftKey && me.lastFocused) {
						me.selectRange(me.lastFocused, record, e.ctrlKey);
					} else if (e.ctrlKey) {
						me.doSelect(record, true, false);
					} else if (me.isSelected(record) && !e.shiftKey && !e.ctrlKey && me.selected.getCount() > 1) {
						me.doSelect(record, keepExisting, false);
					} else {
						me.doSelect(record, false);
					}
					break;
				case 'SIMPLE':
					if (me.isSelected(record)) {
						me.doDeselect(record);
					} else {
						me.doSelect(record, true);
					}
					break;
				case 'SINGLE':
					// if allowDeselect is on and this record isSelected, deselect it
					if (me.allowDeselect && me.isSelected(record)) {
						me.doDeselect(record);
						// select the record and do NOT maintain existing selections
					} else {
						me.doSelect(record, false);
					}
					break;
			}
		}
	})).mixin({
		observable : aries.util.Observable,
		bindable : aries.util.Bindable
	})
})
